<template>
	<view class="chat-index" ref="uv-back-top">
		<headers :state="state" @custom-event="e=>state = e"></headers>
		<el-container
			:style="$uv.os() == 'windows'?'height: calc(100vh - 50px);':'height: calc(100vh - 115px);overflow: auto;'">
			<asided :aicurrent="aicurrent" :asideW="state?70:0" @custom-event="chatai" @custom-type="e=>aiType = e"
				@custom-list="e=>lists = e">
			</asided>
			<view
				:style="(parmShow &&$uv.os() == 'windows')?'max-width: 450px;margin:5px;padding:0px 6px;borber:1px solid #f2f2f2;border-radius: 10px;background-color: rgba(200, 200, 200, 0.3);':'width: 0px;height:0px;overflow: hidden;'">
				<gpts v-if="lists" :chatlist="lists[aicurrent]" @submit="$uv.os() != 'windows'?parmShow = false:''"
					:aicurrent="aicurrent" :asideW="asiW" @custom-event="reschat">
				</gpts>
			</view>
			<el-container style="height: 100%;display: flex;justify-content: center; flex-wrap: wrap;">
				<el-container style="height:  100%;max-width: 750px;margin-right:10px;">
					<el-header style="height: 40px;padding: 0px 5px;">
						<view style="display: flex;justify-content: space-between;padding:5px 0px;">
							<view @click="create"
								style="cursor: pointer; padding:5px 10px;border: 1px solid #eee;border-radius: 5px;background-color: #f3f3f3;">
								新建会话
							</view>
							<view class="cursor-pointer" @click="parmShow = !parmShow"
								style="border-radius: 5px;border: 1px solid #eee;padding: 5px 10px;display: flex;justify-content: flex-start;background-color: #f3f3f3;">
								{{chat.text}}<uv-icon style="margin-left: 5px;" :name="parmShow?'lock-open':'lock'"
									size="20"></uv-icon>
							</view>
							<view class="cursor-pointer"
								style="padding:5px 10px;border: 1px solid #eee;border-radius: 5px;background-color: #f2f2f2;display: flex;justify-content: space-between;align-items: center;">
								<view @click="delex()"
									style="display: flex;justify-content: flex-start;align-items: center;">
									<i class="el-icon-delete" style=""></i>
									<view v-if="$uv.os() == 'windows'" style="transition: width 2s;margin-left: 10px;">
										删除当前会话</view>
								</view>
							</view>
						</view>
					</el-header>
					<el-main
						:style="$uv.os() == 'windows'?'width: 100%;display: flex;justify-content: center;flex-wrap: nowrap;padding: 5px;':'padding: 5px;height: calc(100vh - 205px);'">
						<view style="max-width: 750px;">
							<view :class="(parmShow && $uv.os() != 'windows')?'gpts2 gptstrue':'gpts2 gptsfalse'"
								style="">
								<gpts v-if="lists" :chatlist="lists[aicurrent]"
									@submit="$uv.os() != 'windows'?parmShow = false:''" :aicurrent="aicurrent"
									:asideW="asiW" @custom-event="reschat">
								</gpts>
							</view>
							<view style="overflow: auto;padding: 5px;">
								<view v-for="(item,index) in list" :key="index">
									<uv-text v-if="cTime(item.createdAt,list,index)"
										:customStyle="'margin-bottom: 5px;border-radius:5px;padding:2px 5px;background-color:'+dialogueColor"
										:text="times(item.createdAt)" align="center"></uv-text>
									<view :class="item.role=='user'?'middle_item user':'middle_item'">
										<view class="fill">
											<el-image fit="fill"
												:src="item.role=='user'?(vuex_user.baseUrl+vuex_user.portrait):'/static/ai.svg'"></el-image>
										</view>
										<view
											:style="'background-color:'+dialogueColor+';border-radius: 10px;padding:5px  10px 0px 10px;margin:0px 0px;'">
											<view v-if="item.content">
												<contents :style="'height: auto;background-color:'+dialogueColor"
													:type="tabbarsValue" :taskid="taskid" :item="item" :index="index"
													@del-event="resDelChat">
												</contents>
											</view>
											<view v-else>
												<uv-loading-icon color="red"></uv-loading-icon>
											</view>
										</view>
										<view style="opacity: 0;width: 45px;height: 40px;">00000</view>
									</view>
									<!-- <uv-row v-if="item.role!='user'" justify="start"
													customStyle="margin-left: 45px;margin-bottom: 5px;">
													<uv-button type="primary" text="停止生成" size="mini" @click=""></uv-button>
												</uv-row> -->
								</view>
							</view>
							<el-link style="font-size: 12px;opacity: 0;height: 0px !important;"
								type="primary">{{fill}}</el-link>
						</view>
					</el-main>
					<el-footer
						style="border-radius: 15px;background-color: #eee;padding:10px 10px 5px;margin:10px 5px;height: auto;bottom: 0px;">
						<view class="pos_image" :style="imageUrl[0]?'height: 70px;':'height: 0px;opacity: 0;'">
							<view v-for="(item,idx) in imageUrl" :key="idx"
								style="position: relative;margin-right: 10px;margin-bottom: 5px;">
								<view class="item_dsh" @click="imageUrl.splice(idx, 1)">
									<uv-icon color="#FFF" bold name="close"></uv-icon>
								</view>
								<uv-image @click="preview(item.url)" mode="fill" :src="item.url" width="60px"
									height="60px" radius="6px"></uv-image>
							</view>
							<!-- accept="file" all url="file" uploads -->
						</view>
						<view v-if="imageUrl[0]" style="margin-top: 6px;margin-bottom: 2px;">
							<uv-line color="#2979ff"></uv-line>
						</view>
						<document maxCount="6" accept="jpg,jpeg,webp,png" ref="document" v-model="imageUrl">
						</document>
						<el-input :disabled="song" @focus="$uv.os() != 'windows'?(state = false,parmShow= false):''"
							type="textarea" placeholder="请输入内容" :autosize="{ minRows: 2, maxRows: 5}" v-model="msg"
							show-word-limit>
						</el-input>
						<view style="margin-top: 5px;display: flex;justify-content: space-between;">
							<view class="cursor-pointer" @click="prefixclick">
								<uv-icon :style="uploads?'':'opacity: 0.2;'" name="scan" size="30"></uv-icon>
							</view>
							<uv-button :disabled="song" style="height: 30px;width: 30px;" type="primary" shape="circle"
								@click="taskd"><i class="el-icon-top"></i></uv-button>
						</view>

						<!-- toSend <uv-search @focus="$uv.os() != 'windows'?(state = false,parmShow= false):''"
								class="cursor-pointer" name="scan" :disabled="song" placeholder="请输入你想问的..."
								v-model="msg" clearabled actionText="发送" searchIcon="scan" searchIconSize="30"
								@custom="taskd" @search="taskd" @clickIcon="prefixclick" :actionStyle="{
										'fontSize': '15px'
									}">
							</uv-search> -->
					</el-footer>
				</el-container>
			</el-container>

			<view v-if="$uv.os() == 'windows'">
				<el-aside :width="footers?historyW*2.4+'px':historyW+'px'"
					style="background-color: #fff;box-shadow: 10px 10px 20px #ccc;padding:10px 10px 0px;transition: width 1s;top:52px; right: 0px;border: 1px solid #f2f2f2;border-top-left-radius:10px;border-bottom-left-radius:10px;position: fixed;">
					<view style="position: fixed;margin-left: 0px;margin-top: 45vh;">
						<view class="cursor-pointer" style="margin-left: -30px;background-color: #f2f2f2;padding: 8px;"
							@click="footers = !footers"
							:style="'width:200px;display: flex;justify-content: center;align-items: center;border-radius: 50%;width:20px ; height: 20px;'">
							<i v-if="footers" class="el-icon-arrow-right" style="font-size: 25px;"></i>
							<i v-else class="el-icon-arrow-left" style="font-size: 25px;"></i>
						</view>
					</view>
					<view class=""
						style="height: calc(100vh - 110px);padding: 2px 5px;display: flex;justify-content: flex-start;align-items: center;overflow: auto;">
						<history :footers="footers" :refresh="refresh" :title="'对话'" :assembly="false" @resid="delex"
							@submit="getVuex">
						</history>
					</view>
				</el-aside>
				<el-aside :width="historyW+'px'"></el-aside>
			</view>
			<el-aside v-else :width="footers?historyW+'px':'0px'"
				style="height: calc(100vh - 112px);box-shadow: 10px 10px 20px #ccc;padding:5px 5px 0px;transition: width 1s;top:52px; right: 0px;border: 1px solid #f2f2f2;border-top-left-radius:10px;border-bottom-left-radius:10px;position: fixed;background-color: #fff">
				<view style="position: fixed;margin-left: 0px;margin-top: 45vh;z-index:1;">
					<view style="margin-left: -25px;background-color: #f2f2f2;padding: 7px;" @click="footers = !footers"
						:style="'width:200px;display: flex;justify-content: center;align-items: center;border-radius: 50%;width:20px ; height: 20px;'">
						<i v-if="footers" class="el-icon-arrow-right" style="font-size: 25px;"></i>
						<i v-else class="el-icon-arrow-left" style="font-size: 25px;"></i>
					</view>
				</view>
				<view
					style="height: calc(100vh - 122px);display: flex;justify-content: flex-start;align-items: center;overflow: auto;">
					<history :footers="false" :refresh="refresh" :title="'对话'" :assembly="false" @resid="delex"
						@submit="getVuex">
					</history>
				</view>
			</el-aside>
		</el-container>
		<uv-toast ref="toast"></uv-toast>
		<tabbars></tabbars>
	</view>
</template>

<script>
	// 引入
	import contents from '@/components/content/content';
	import history from '@/components/history/history';
	import tabbars from '@/components/tabbar/tabbar';
	import document from '@/components/document/document.vue';
	import {
		index,
		task,
		api,
		apiId,
		getScroll,
		scroll,
		scrollName,
		delScroll
	} from '@/common/api.js';
	import headers from '@/components/header/header.vue';
	import asided from '@/pages/aside/aside.vue';
	import gpts from './aside/aside.vue';
	import config from '@/common/config'
	import {
		fetchEventSource
	} from '@microsoft/fetch-event-source';
	export default {
		components: {
			// 注册组件
			tabbars,
			contents,
			history,
			headers,
			asided,
			gpts,
			document
		},
		data() {
			return {
				fill: config.fill,
				env: config.env == 'development',
				dialogueColor: '#F4F5F9',
				deepColor: '#4D4D4D',
				shallowColor: '#808080',
				lightColor: '#ccc',
				state: true,
				asideW: 70,
				asiW: 420,
				x: 30, // 初始边框宽度
				activeIndex: 0,
				startX: 0, // 触摸开始的X坐标
				endX: 0, // 触摸结束的X坐标
				footers: false,
				tabbarsValue: 'chat',
				msg: '',
				content: '',
				tips: '',
				seconds: 10,
				aicurrent: 0,
				req: {},
				historyW: 155,
				sse: null,
				eventSourceStatus: true,
				song: false,
				aiType: 'OpenAI',
				chat: {},
				uploads: false,
				lists: {},


				parmShow: false,
				list: null, // 聊天内容
				taskid: null,
				refresh: '',
				quota: 0,
				imageUrl: [],
			}
		},
		watch: {
			// taskid(newVal, oldVal) {
			// 	this.refresh = uni.$uv.guid(20);
			// },
		},
		mounted() {
			this.tabbarsValue = this.pages();
			var model = this.aiList;
			var chat = this['config_' + this.tabbarsValue];
			for (var i = 0; i < model.length; i++) {
				if (chat.ai && model[i].value == chat.ai &&
					chat.tag) {
					this.chat = {
						ai: chat.ai,
						text: chat.text,
						model: chat.model,
						tag: chat.tag,
						series: chat.series,
						messages: chat.messages,
						n: chat.n,
						temperature: chat.temperature,
						top_p: chat.top_p,
						presence_penalty: chat.presence_penalty,
						frequency_penalty: chat.frequency_penalty,
					};
				}
			}
			if (this.chatCurrent != null) {
				this.aicurrent = this.chatCurrent
			}
		},
		methods: {
			// 预览图片
			preview(e) {
				uni.previewImage({
					// current: i,
					urls: [e]
				});
			},
			chatai(e) {
				this.aicurrent = e;
				this.$uv.vuex('chatCurrent', e);
			},
			reschat(e) {
				this.$uv.vuex('config_' + this.tabbarsValue, e);
				this.chat = e;
				if (this.chat['tag'] && this.chat['tag'].includes('imageAnalysis')) {
					this.uploads = true;
				} else {
					this.uploads = false;
				}
			},
			prefixclick() {
				if (this.uploads) {
					this.$nextTick(() => {
						this.$refs.document.visible();
					});
				}
			},
			delex(i) {
				if (i.includes(this.taskid)) {
					this.list = [];
					this.taskInfo = {};
				}
			},
			async create() {
				await scrollName('addModify', {
					type: this.tabbarsValue,
				}, {
					custom: {
						auth: true,
						toast: false,
					},
				}).then(e => {
					if (e) {
						this.taskid = e._id;
						this.list = [];
						this.refresh = uni.$uv.guid(20);
					}
				})
			},
			async toSend() {
				if (!this.msg) {
					this.$refs.toast.show({
						type: 'error',
						message: "要提问的内容为空！"
					})
				}
				const that = this;
				var con = JSON.parse(JSON.stringify(this['config_' + this.tabbarsValue]), true);
				let dfds = null;
				if (this.imageUrl[0]) {
					dfds = {
						role: 'user',
						content: [{
							"type": "text",
							"text": this.msg
						}], // createdAt: new Date,
					}
					for (var i = 0; i < this.imageUrl.length; i++) {
						dfds.content.push({
							"type": "image_url",
							"image_url": {
								"url": this.imageUrl[i].url
								// "url": 'https://api.readend.com/dpc/temp/mini_image_1736933435723.png'
							}
						});
					}
				} else {
					dfds = {
						role: 'user',
						content: this.msg,
						// createdAt: new Date,
					}
				}
				if (con['messages']) {
					con['messages'] = [...con.messages, dfds];
				} else {
					con['messages'] = [dfds];
				}
				var task = await uni.request({
					url: `/dpc${ this.aiApi }/stream/chat`, // 会话接口
					enableChunked: true, // 这意味着服务器会采用分块传输编码来发送数据。
					method: 'POST',
					responseType: "text",
					dataType: "json", // 请求的数据类型
					timeout: 900000, // 设置超时时间为10秒
					header: {
						'Content-Type': 'application/json',
						'Authorization': "Bearer " + this.aes_decrypt(this.vuex_token),
						'Connection': 'keep-alive',
					},
					data: JSON.stringify({
						"ai": con['ai'],
						"model": con['model'],
						"series": con['series'],
						"messages": JSON.stringify(con['messages']),
						"type": 'chat',
						"chat_id": this.taskid ? this.taskid : null,
					}),
					success: (xhr) => {
						console.log('xhr', xhr)
						if (xhr.statusCode === 200) {

							var json = xhr.data;
							var response = json.choices[0].text;

							// 将CHATGPT的返回值输出到文本框
							var indexxx = 0;
							that.answer = ''
							// 创建一个定时器，每隔一段时间打印一个字符
							var intervals = setInterval(function() {
									that.answer += response[indexxx];
									indexxx++;

									// 当打印完成时，清除定时器
									if (indexxx >= response.length) {
										clearInterval(intervals);
									}
								},
								50); // 每隔50毫秒打印一个字符
						}
						console.log(xhr);
					},
					fail: (err) => {
						console.log(err);
					}
				})

				const cleanupInterval = setInterval(function() {
					if (task._xhr.readyState == 4) {
						clearInterval(cleanupInterval);
					}
					var res = task._xhr.responseText;
					let text = res.data;
					console.log(task._xhr);
				}, 500)
				// console.log(task._xhr.readyState);
				// console.log(task._xhr.responseText);

			},
			async taskd() {
				var that = this;
				if (!this.msg) {
					this.$refs.toast.show({
						type: 'error',
						message: "要提问的内容为空！"
					})
				}
				this.$refs.toast.show({
					message: "发送中。。。",
					duration: '2000'
				});
				this.song = true;
				var con = JSON.parse(JSON.stringify(this['config_' + this.tabbarsValue]), true);
				let dfds = null;
				if (this.imageUrl[0]) {
					dfds = {
						role: 'user',
						content: [{
							"type": "text",
							"text": this.msg
						}], // createdAt: new Date,
					}
					for (var i = 0; i < this.imageUrl.length; i++) {
						dfds.content.push({
							"type": "image_url",
							"image_url": {
								"url": this.imageUrl[i].url
								// "url": 'https://api.readend.com/dpc/temp/mini_image_1736933435723.png'
							}
						});
					}
				} else {
					dfds = {
						role: 'user',
						content: this.msg,
						// createdAt: new Date,
					}
				}
				if (con['messages']) {
					con['messages'] = [...con.messages, dfds];
				} else {
					con['messages'] = [dfds];
				}
				// 定义一个url，'/v1/chat/completions' '/stream/chat'
				const url = "/dpc" + this.aiApi + (this.env ? '/v1/chat/completions' :
					'/v1/chat/completions')
				const ctrlAbout = new AbortController();
				fetchEventSource(url, {
					method: 'POST',
					headers: {
						'Content-Type': 'application/json',
						'Authorization': "Bearer " + this.aes_decrypt(this.vuex_token),
						'Connection': 'keep-alive',
					},
					body: JSON.stringify({
						"ai": con['ai'],
						"model": con['model'],
						"series": con['series'],
						"messages": JSON.stringify(con['messages']),
						"type": 'chat',
						"chat_id": this.taskid ? this.taskid : null,
					}),
					signal: ctrlAbout.signal,
					openWhenHidden: true, // 在浏览器标签页隐藏时保持与服务器的EventSource连接
					async onopen(response) {
						//建立连接的回调
						dfds['type'] = "text";
						dfds['createdAt'] = new Date();
						if (!that.list) {
							that.list = [];
						}
						that.list.push(dfds);
						that.msg = null;
						that.imageUrl = [];
						that.list.push({
							type: 'text',
							role: 'assistant',
							content: '',
							quota: 0
						});
						that.refresh = uni.$uv.guid(20);
					},
					onmessage(e) {
						console.log('onmessage', e);
						try {
							if (e.data === '[DONE]') {
								that.song = false;
								ctrlAbout.abort();
								return;
							}
							if (!that.eventSourceStatus) {
								// 手动终止
								that.song = false;
								ctrlAbout.abort();
								return;
							}
							const res = e.data && JSON.parse(e.data);
							console.log("流式数据", res);
							if (res?.code == 201 || res?.code == 401) {
								that.list[that.list.length - 1].content = res?.msg;
								that.$refs.toast.show({
									type: 'error',
									message: res?.msg || "发送失败！",
									duration: '2000'
								});
								that.song = false;
								ctrlAbout.abort();
								return;
							}
							if (res?.quota) {
								const quota = res?.quota ? res.quota : 0;
								that.list[that.list.length - 1].quota = quota;
							} else {
								const {
									content = ''
								} = (res?.choices && res?.choices[0]?.delta) ? res?.choices[0]?.delta: '';
								that.list[that.list.length - 1].content += content;
							}
						} catch (err) {
							console.log(e.data, e.data.msg);
						}
					},
					onerror(err) {
						//连接出现异常回调
						that.song = false;
						console.log((new Date().getTime()), 'error', err)
						// 取消请求
						throw err
					},
					onclose(e) {
						console.log(e);
						that.song = false;
						that.eventSourceStatus = true;
						ctrlAbout.abort();
						return;
					}
				});
			},
			async getVuex(i = '') {
				var params = {
					type: this.tabbarsValue,
				};
				if (i) {
					params['id'] = i;
				}
				getScroll({
					custom: {
						auth: true,
						toast: false,
					},
					params: params
				}).then(e => {
					if (e && e.content != []) {
						this.list = e.content;
						this.taskid = e._id;
					}
				})
			},
			pages() {
				var pages = getCurrentPages() // 获取栈实例
				let currentRoute = pages[pages.length - 1].route; // 获取当前页面路由
				let currentPage = pages[pages.length - 1]['$page']['fullPath'] //当前页面路径(带参数)
				return currentPage.split('/')[2];
			},
			cTime(time1, e, index) {
				if (index == 0) {
					return true;
				}
				// console.log(" ” " + Number(e.length - 1));
				if (index == Number(e.length - 1)) {
					return false;
				}
				var time2 = e[index + 1] && e[index + 1].createdAt;
				if (!time1 || !time2) {
					return false;
				}
				// console.log(time1 + " 《=》 " + time2);
				// 将时间转换为Date对象
				var date1 = new Date(time1).getTime();
				var date2 = new Date(time2).getTime();
				// 计算时间差（毫秒）
				var difference = Math.abs(date2 - date1);
				// 将时间差转换为分钟
				var minutes = Math.ceil(difference / (1000 * 60)); // 向上取整
				if (minutes > 20) {
					return true;
				} else {
					return false;
				}
			},
			times(time) {
				return " -- " + uni.$uv.timeFrom(new Date(time).getTime()) + " -- ";
			},
			routes(url) {
				this.routed('/pages' + url);
			},
			routed(url) {
				console.log('routed', url)
				uni.$uv.route({
					url: url,
					intercept(params, next) {
						// console.log(params, next)
						uni.showLoading({
							title: '等待中...'
						})
						// 模拟处理等待
						setTimeout(() => {
							uni.hideLoading();
							next(true);
						}, 1000)
					}
				})
			}
		}
	}
</script>

<style scoped lang="scss">
	.chat-index {
		height: 100vh;

		.gpts2 {
			max-width: 450px;
			margin: 5px;
			padding: 0px 6px;
			borber: 1px solid #f2f2f2;
			border-radius: 10px;
			background-color: rgba(200, 200, 200, 0.3);
		}

		.gptstrue {}

		.gptsfalse {
			width: 0px;
			height: 0px;
			overflow: hidden;
		}

		.middle_item {
			margin-bottom: 5px;
			display: flex;
			align-items: flex-start;
			// width: 100%;
			flex-direction: row;
			flex-wrap: nowrap;
			align-content: stretch;
			justify-content: flex-start;

			.selectable-text {
				-webkit-user-select: text;
				/* Safari */
				-moz-user-select: text;
				/* Firefox */
				-ms-user-select: text;
				/* IE 10+ */
				user-select: text;
				/* Standard syntax */
			}

			::v-deep .code-block {
				background-color: #f8f8f8;
				border: 1px solid #ddd;
				padding: 5px 10px;
				border-radius: 5px;
				white-space: pre-wrap;
				word-break: break-all;
				font-family: 'Courier New', Courier, monospace;
				font-size: 14px;
			}



			.fill {
				width: 40px;
				height: 40px;
				margin: 0px;
				padding: 2px;
				border-radius: 20px;

				::v-deep .el-image__inner {
					width: 40px;
					height: 40px;
					border-radius: 20px;
				}
			}

		}

		.user {
			flex-direction: row-reverse;
		}

		.pos_image {
			display: flex;
			justify-content: flex-start;
			align-items: center;
			background-color: #fff;
			border-radius: 10px;
			padding: 5px 0px 0px 5px;

			.item_dsh {
				position: absolute;
				z-index: 10;
				top: 0px;
				right: 0px;
				background-color: #000;
				padding: 5px 6px;
				color: #fff;
				border-top-right-radius: 10px;
				border-bottom-left-radius: 10px;
				cursor: pointer;
			}
		}

		.el-textarea {
			width: 100%;

			::v-deep .el-textarea__inner {
				border: 0px;
				color: #000;
				padding: 0px;
				width: 100%;
				background-color: rgba(0, 0, 0, 0) !important;
			}
		}

	}
</style>